package socnet.model;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.graphdb.index.IndexManager;

public class SocnetDAO {

	//private static final String DB_PATH = ".\\ext\\db\\";
	private static final String DB_PATH = ".\\ext\\db_test\\";

	GraphDatabaseService graphDB;
	Relationship myRelationship;
	Node node;
	Node user_reference_node;
	Node group_reference_node;
	IndexManager index;
	Index<Node> names;
	Index<Node> emails;
	Index<Node> urls;
	Index<Node> groups;
	Index<Node> posts;
	Index<Node> comments;

	private static enum RelTypes implements RelationshipType {
		KNOWS, UPLOADED, REFERENCE, USER, MESSAGE, GROUP, ADMIN_OF, MEMBER_OF, POSTED, COMMENTED
	}

	public void createDb() {
		graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(DB_PATH);
		index = graphDB.index();
		names = index.forNodes("name");
		emails = index.forNodes("email");
		urls = index.forNodes("url");
		groups = index.forNodes("name");
		posts = index.forNodes("post");
		comments = index.forNodes("comment");
		registerShutdownHook(graphDB);

		try {
			user_reference_node = graphDB.getNodeById(1);
			group_reference_node = graphDB.getNodeById(2);
		} catch (Exception e) {
			Transaction tx = graphDB.beginTx();
			try {
				user_reference_node = graphDB.createNode();
				user_reference_node.setProperty("name", "USER_REFERENCE_NODE");
				graphDB.getReferenceNode().createRelationshipTo(
						user_reference_node, RelTypes.REFERENCE);
				group_reference_node = graphDB.createNode();
				group_reference_node
						.setProperty("name", "GROUP_REFERENCE_NODE");
				graphDB.getReferenceNode().createRelationshipTo(
						group_reference_node, RelTypes.REFERENCE);
				tx.success();
			} finally {
				tx.finish();
			}
		}

	}

	@SuppressWarnings("deprecation")
	public void removeData() {
		Transaction tx = graphDB.beginTx();
		try {
			// myFirstNode.getSingleRelationship(RelTypes.KNOWS,
			// Direction.OUTGOING).delete();
			System.out.println("Removing nodes...");
			Iterable<Node> iterable = graphDB.getAllNodes();
			Iterator<Node> nodes = iterable.iterator();
			Iterable<Relationship> it;
			Iterator<Relationship> rels;
			Node n;
			while (nodes.hasNext()) {
				n = nodes.next();
				it = n.getRelationships();
				rels = it.iterator();
				while (rels.hasNext()) {
					rels.next().delete();
				}
				n.delete();
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void shutDown() {
		graphDB.shutdown();
		System.out.println("graphDB shut down.");
	}

	private static void registerShutdownHook(final GraphDatabaseService graphDb) {
		// Registers a shutdown hook for the Neo4j instance so that it
		// shuts down nicely when the VM exits (even if you "Ctrl-C" the
		// running application).
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				graphDb.shutdown();
			}
		});
	}

	public void addUser(User user) {
		Transaction tx = graphDB.beginTx();

		try {
			node = graphDB.createNode();
			node.setProperty("id", user.getId());
			node.setProperty("email", user.getEmail());
			node.setProperty("birthdate", user.getBirthdate());
			node.setProperty("passw", user.getPassw());
			if (user.getCity() != null)
				node.setProperty("city", user.getCity());
			node.setProperty("admin", user.isAdmin());
			if (user.getAbout() != null)
				node.setProperty("about", user.getAbout());
			node.setProperty("first_name", user.getFirst_name());
			node.setProperty("last_name", user.getLast_name());
			node.setProperty("name",
					user.getLast_name() + " " + user.getFirst_name());
			if (user.getGender() != null)
				node.setProperty("gender", user.getGender());
			if (user.getWork() != null)
				node.setProperty("work", user.getWork());

			user_reference_node.createRelationshipTo(node, RelTypes.USER);

			names.add(node, "name", node.getProperty("name"));
			emails.add(node, "email", node.getProperty("email"));

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void updateUser(User user) {
		Transaction tx = graphDB.beginTx();

		try {
			node = emails.query("email", user.getEmail()).getSingle();
			node.setProperty("id", user.getId());
			node.setProperty("birthdate", user.getBirthdate());
			node.setProperty("passw", user.getPassw());
			if (user.getCity() != null)
				node.setProperty("city", user.getCity());
			node.setProperty("admin", user.isAdmin());
			if (user.getAbout() != null)
				node.setProperty("about", user.getAbout());
			node.setProperty("first_name", user.getFirst_name());
			node.setProperty("last_name", user.getLast_name());
			node.setProperty("name",
					user.getLast_name() + " " + user.getFirst_name());
			if (user.getGender() != null)
				node.setProperty("gender", user.getGender());
			if (user.getWork() != null)
				node.setProperty("work", user.getWork());

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void deleteUser(User user) {
		Transaction tx = graphDB.beginTx();

		try {
			node = emails.query("email", user.getEmail()).getSingle();

			Iterator<Relationship> it = node.getRelationships().iterator();
			while (it.hasNext()) {
				it.next().delete();
			}

			emails.remove(node);
			names.remove(node);
			node.delete();

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public boolean addRelationship(String user1, String user2) {
		Transaction tx = graphDB.beginTx();
		boolean state = false;

		// Relationship r1, r2;
		Node found1, found2;

		found1 = emails.query("email", user1).getSingle();
		found2 = emails.query("email", user2).getSingle();

		try {
			found1.createRelationshipTo(found2, RelTypes.KNOWS);
			found2.createRelationshipTo(found1, RelTypes.KNOWS);
			// r1.setProperty("relationship-type", "knows");
			// r2.setProperty("relationship-type", "knows");

			state = true;

			tx.success();
		} finally {
			tx.finish();
		}

		return state;
	}

	public boolean deleteRelationship(String user1, String user2) {
		Transaction tx = graphDB.beginTx();
		boolean state = false;
		Node n1, n2;
		Relationship r;

		n1 = emails.query("email", user1).getSingle();
		n2 = emails.query("email", user2).getSingle();

		Iterable<Relationship> it = n1.getRelationships();
		Iterator<Relationship> rels = it.iterator();

		try {
			while (rels.hasNext()) {
				r = rels.next();
				if (r.getEndNode().getProperty("email").equals(user2)) {
					r.delete();
				}
			}

			it = n2.getRelationships();
			rels = it.iterator();

			while (rels.hasNext()) {
				r = rels.next();
				if (r.getEndNode().getProperty("email").equals(user1)) {
					r.delete();
				}
			}
			state = true;
			tx.success();
		} finally {
			tx.finish();
		}

		return state;
	}

	public List<User> getFriends(String email) {
		Transaction tx = graphDB.beginTx();
		List<User> friends = new ArrayList<User>();
		Node n;
		User user;

		try {
			Node found = emails.query("email", email).getSingle();
			Iterator<Relationship> rels = found.getRelationships(
					Direction.OUTGOING, RelTypes.KNOWS).iterator();

			while (rels.hasNext()) {
				n = rels.next().getEndNode();
				user = new User(n.getProperty("email").toString(), n
						.getProperty("birthdate").toString(), n.getProperty(
						"passw").toString(), n.getProperty("admin").toString()
						.equals("true"),
						n.getProperty("first_name").toString(), n.getProperty(
								"last_name").toString());

				if (n.hasProperty("city"))
					user.setCity(n.getProperty("city").toString());
				if (n.hasProperty("about"))
					user.setAbout(n.getProperty("about").toString());
				if (n.hasProperty("gender"))
					user.setGender(n.getProperty("gender").toString());
				if (n.hasProperty("work"))
					user.setWork(n.getProperty("work").toString());

				friends.add(user);
			}

			tx.success();
		} finally {
			tx.finish();
		}

		return friends;
	}

	public List<User> getUser(String par, int mode) {
		IndexHits<Node> hits;
		if (mode == 0) {
			hits = names.query("name", par);

		} else
			hits = emails.query("email", par);

		Iterator<Node> it = hits.iterator();

		List<User> users = new ArrayList<User>();

		while (it.hasNext()) {
			Node found = it.next();
			User user = new User(found.getProperty("email").toString(), found
					.getProperty("birthdate").toString(), found.getProperty(
					"passw").toString(), found.getProperty("admin").toString()
					.equals("true"),
					found.getProperty("first_name").toString(), found
							.getProperty("last_name").toString());

			if (found.hasProperty("city"))
				user.setCity(found.getProperty("city").toString());
			if (found.hasProperty("about"))
				user.setAbout(found.getProperty("about").toString());
			if (found.hasProperty("gender"))
				user.setGender(found.getProperty("gender").toString());
			if (found.hasProperty("work"))
				user.setWork(found.getProperty("work").toString());

			users.add(user);
		}

		return users;
	}

	public boolean emailIsValid(String par) {
		boolean res;

		try {
			Node found = emails.query("email", par).getSingle();
			found.getProperty("email");
			res = false;
		} catch (Exception e) {
			res = true;
		}

		return res;
	}

	public boolean checkPassw(String email, String passw) {
		boolean res;

		try {
			Node found = emails.query("email", email).getSingle();
			if (found.getProperty("passw").equals(passw))
				res = true;
			else
				res = false;
		} catch (Exception e) {
			res = false;
		}

		return res;
	}

	public boolean addPhoto(String user, Photo photo) {
		Transaction tx = graphDB.beginTx();
		boolean state = false;
		String name;

		Node found = emails.query("email", user).getSingle();
		name = found.getProperty("name").toString();

		try {
			node = graphDB.createNode();
			node.setProperty("url", photo.getUrl());
			node.setProperty("author", name);
			if (photo.getAbout() != null)
				node.setProperty("about", photo.getAbout());
			if (photo.getTitle() != null)
				node.setProperty("title", photo.getTitle());

			found.createRelationshipTo(node, RelTypes.UPLOADED);

			urls.add(node, "url", node.getProperty("url"));

			state = true;

			tx.success();
		} finally {
			tx.finish();
		}

		return state;
	}

	public boolean updatePhoto(Photo photo) {
		Transaction tx = graphDB.beginTx();
		boolean state = false;

		Node found = urls.get("url", photo.getUrl()).getSingle();

		try {
			if (photo.getAbout() != null)
				found.setProperty("about", photo.getAbout());
			if (photo.getTitle() != null)
				found.setProperty("title", photo.getTitle());

			state = true;

			tx.success();
		} finally {
			tx.finish();
		}

		return state;
	}

	public boolean deletePhoto(String url) {
		Transaction tx = graphDB.beginTx();
		boolean state = false;

		Node found = urls.get("url", url).getSingle();
		Iterator<Relationship> it = found.getRelationships().iterator();

		try {
			while (it.hasNext()) {
				it.next().delete();
			}
			urls.remove(found);
			found.delete();

			state = true;

			tx.success();
		} finally {
			tx.finish();
		}

		return state;
	}

	public List<Photo> getPhotos(String email) {
		Transaction tx = graphDB.beginTx();
		Photo p;
		Node n;
		List<Photo> photos = new ArrayList<Photo>();
		Iterator<Relationship> rels;
		Node found = emails.query("email", email).getSingle();

		try {
			rels = found
					.getRelationships(Direction.OUTGOING, RelTypes.UPLOADED)
					.iterator();
			while (rels.hasNext()) {
				n = rels.next().getEndNode();

				p = new Photo(n.getProperty("url").toString());
				if (n.hasProperty("title"))
					p.setTitle(n.getProperty("title").toString());
				if (n.hasProperty("about"))
					p.setTitle(n.getProperty("about").toString());

				photos.add(p);
			}

			tx.success();
		} finally {
			tx.finish();
		}

		return photos;
	}

	public boolean addMsg(Message msg) {
		boolean res = false;
		Transaction tx = graphDB.beginTx();
		Node found1, found2;

		found1 = emails.query("email", msg.getFrom()).getSingle();
		found2 = emails.query("email", msg.getTo()).getSingle();

		try {
			Relationship msg1 = found1.createRelationshipTo(found2,
					RelTypes.MESSAGE);
			msg1.setProperty("text", msg.getText());
			msg1.setProperty("date", msg.getDate());

			res = true;

			tx.success();
		} finally {
			tx.finish();
		}

		return res;
	}

	public List<Message> getMsgs(String email) {
		Transaction tx = graphDB.beginTx();
		Message msg;
		List<Message> msgs = new ArrayList<Message>();
		Iterator<Relationship> it;

		Node found = emails.query("email", email).getSingle();

		try {
			it = found.getRelationships(RelTypes.MESSAGE).iterator();

			while (it.hasNext()) {
				Relationship r = it.next();
				msg = new Message();
				msg.setFrom(r.getStartNode().getProperty("email").toString());
				msg.setTo(r.getEndNode().getProperty("email").toString());
				msg.setDate(r.getProperty("date").toString());
				msg.setText(r.getProperty("text").toString());

				msgs.add(msg);
			}
			tx.success();
		} finally {
			tx.finish();
		}

		return msgs;
	}

	public void addGroup(Group group) {
		Transaction tx = graphDB.beginTx();

		try {
			Node n = graphDB.createNode();

			n.setProperty("creation_date", group.getCreation_date());
			n.setProperty("name", group.getName());

			List<User> admins = group.getAdmins();
			Iterator<User> it = admins.iterator();
			while (it.hasNext()) {
				User u = it.next();
				Node found = emails.query("email", u.getEmail()).getSingle();
				found.createRelationshipTo(n, RelTypes.ADMIN_OF);
			}

			List<User> users = group.getUsers();
			Iterator<User> it2 = users.iterator();
			while (it2.hasNext()) {
				User u = it2.next();
				Node found = emails.query("email", u.getEmail()).getSingle();
				found.createRelationshipTo(n, RelTypes.MEMBER_OF);
			}

			group_reference_node.createRelationshipTo(n, RelTypes.GROUP);

			groups.add(n, "name_g", n.getProperty("name"));

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void deleteGroup(Group group) {
		Transaction tx = graphDB.beginTx();

		try {
			Node found = groups.query("name_g", group.getName()).getSingle();
			Iterable<Relationship> rels = found.getRelationships();
			Iterator<Relationship> it = rels.iterator();
			while (it.hasNext())
				it.next().delete();
			names.remove(found);
			found.delete();
			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void joinGroup(Group group, String email) {
		Transaction tx = graphDB.beginTx();

		try {
			Node g = groups.query("name_g", group.getName()).getSingle();
			Node u = emails.query("email", email).getSingle();

			u.createRelationshipTo(g, RelTypes.MEMBER_OF);

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void leaveGroup(Group group, String email) {
		Transaction tx = graphDB.beginTx();

		try {
			Node g = groups.query("name_g", group.getName()).getSingle();
			Node u = emails.query("email", email).getSingle();

			Iterable<Relationship> rels = u.getRelationships(
					Direction.OUTGOING, RelTypes.MEMBER_OF);
			Iterator<Relationship> it = rels.iterator();
			while (it.hasNext()) {
				Relationship r = it.next();
				if (r.getEndNode().getProperty("name") == g.getProperty("name"))
					r.delete();
			}

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void addPost(Group g, String email, Post p) {
		Transaction tx = graphDB.beginTx();

		try {
			Node gr = groups.query("name_g", g.getName()).getSingle();
			Node us = emails.query("email", email).getSingle();

			Node po = graphDB.createNode();
			po.setProperty("time", p.getTime());
			po.setProperty("text", p.getText());
			po.setProperty("author", p.getAuthor());
			po.setProperty("id", p.getId());
			
			us.createRelationshipTo(po, RelTypes.POSTED);
			po.createRelationshipTo(gr, RelTypes.POSTED);
			
			posts.add(po, "id_p", p.getId());

			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void updatePost(Post p){
		Transaction tx = graphDB.beginTx();

		try {
			Node po = posts.query("id_p", p.getId()).getSingle();
			po.setProperty("time", p.getTime());
			po.setProperty("text", p.getText());
			po.setProperty("author", p.getAuthor());

			tx.success();
		} finally {
			tx.finish();
		}
	}

	public void deletePost(Post p){
		Transaction tx = graphDB.beginTx();

		try {
			Node po = posts.query("id_p", p.getId()).getSingle();
			
			Iterable<Relationship> rels = po.getRelationships();
			Iterator<Relationship> it = rels.iterator();
			while (it.hasNext()) {
				it.next().delete();
			}
			
			posts.remove(po);
			po.delete();

			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void addComment(Comment c, Post p, String email){
		Transaction tx = graphDB.beginTx();
		
		try{
			Node po = posts.query("id_p", p.getId()).getSingle();
			Node us = emails.query("email", email).getSingle();
			Node co = graphDB.createNode();
			co.setProperty("time", c.getTime());
			co.setProperty("text", c.getText());
			co.setProperty("author", c.getAuthor());
			co.setProperty("id", c.getId());
			
			us.createRelationshipTo(co, RelTypes.COMMENTED);
			co.createRelationshipTo(po, RelTypes.COMMENTED);
			
			comments.add(co, "id_c", c.getId());
			
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void updateComment(Comment c){
		Transaction tx = graphDB.beginTx();
		
		try{
			Node co = comments.query("id_c", c.getId()).getSingle();
			co.setProperty("time", c.getTime());
			co.setProperty("text", c.getText());
			co.setProperty("author", c.getAuthor());
			
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void deleteComment(Comment c){
		Transaction tx = graphDB.beginTx();
		
		try{
			Node co = comments.query("id_c", c.getId()).getSingle();
			
			Iterable<Relationship> rels = co.getRelationships();
			Iterator<Relationship> it = rels.iterator();
			while (it.hasNext()) {
				it.next().delete();
			}
			
			comments.remove(co);
			co.delete();
			
			tx.success();
		} finally {
			tx.finish();
		}
	}
}
